<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
  </head>
  <body></body>
  <script>
    // 原始数据
    const obj = {};
    const proto = { bar: 1 };
    const child = reactive(obj);
    const parent = reactive(proto);
    // 使用 parent 作为 child 的原型
    Object.setPrototypeOf(child, parent);

    // 记录 for...in 循环的给的一个唯一值
    const ITERATE_KEY = Symbol();
    // 存储副作用函数 重新设计的桶
    const bucket = new WeakMap();
    // 存储副作用函数的变量
    let activeEffect;

    // effect 栈
    const effectStack = []; // 新增

    // 清除依赖函数
    function cleanup(effectFn) {
      // 遍历 effectFn.deps 数组
      for (let i = 0; i < effectFn.deps.length; i++) {
        // deps 是依赖集合
        const deps = effectFn.deps[i];
        // 将 effectFn 从依赖集合中移除
        deps.delete(effectFn);
      }
      // 每次执行副作用函数都要清除一遍，最后需要重置 effectFn.dep 数组
      effectFn.deps.length = 0;
    }

    // 注册副作用函数的函数
    const effect = (fn, options = {}) => {
      const effectFn = () => {
        // 调用 cleanup 函数完成清除工作
        cleanup(effectFn);
        // 当 effectFn 执行时，将其设置为当前激活的副作用函数
        activeEffect = effectFn;
        // 在调用副作用函数之前将当前副作用函数压入栈中
        effectStack.push(effectFn); // 新增
        fn();
        // 在当前副作用函数执行完毕后，将当前副作用函数弹出栈，并把 activeEffect 还原为之前的值
        effectStack.pop(); // 新增
        activeEffect = effectStack[effectStack.length - 1];
      };
      // 将 options 挂在到 effectFn 上
      effectFn.options = options; // 新增
      // activeEffect.deps 用来存储所有与该副作用函数相关联的依赖集合
      effectFn.deps = [];
      // 执行副作用函数
      effectFn();
    };

    // track 获取
    const track = (target, key) => {
      // 没有副作用函数直接返回
      if (!activeEffect) return;
      // 获取桶以targe作为键名，以set作为值
      let desMap = bucket.get(target);
      // 如果这个桶中之前没有收集这个对象的依赖那么就新建一个  Map
      if (!desMap) {
        bucket.set(target, (desMap = new Map()));
      }
      // 获取桶中对应对象的 desMap 中的 以键名作为key,以副作用函数集合作为值的数据
      let deps = desMap.get(key);
      // 如果该key为空，那么重新设置set集合
      if (!deps) {
        desMap.set(key, (deps = new Set()));
      }
      // 把当前激活的副作用函数添加到依赖集合 deps 中
      deps.add(activeEffect);
      // deps 就是一个与当前副作用函数存在联系的依赖集合
      // 将其添加到 activeEffect.deps 数组中
      activeEffect.deps.push(deps); // 新增
    };

    // trigger 设置
    const trigger = (target, key, type) => {
      // 根据响应对象获取 依赖集合
      const desMap = bucket.get(target);
      if (!desMap) return true;
      // 根据key获取对应属性的依赖集合
      const effects = desMap.get(key);

      // 避免无限循环
      const effectsToRun = new Set();

      // 将于 key 相关联的副作用函数添加到 effectsToRun
      effects &&
        effects.forEach((effectFn) => {
          // 如果 trigger 触发执行的副作用函数与当前正在执行的副作用函数相同，则不触发执行
          // 新增
          if (effectFn !== activeEffect) {
            effectsToRun.add(effectFn);
          }
        });

      if (type === "ADD" || type === "DELETE") {
        // 取得与 ITERATE_KEY 相关联的副作用函数
        const iterateEffects = desMap.get(ITERATE_KEY);
        // 将与 ITERATE_KEY 相关联的副作用函数也添加到 effectsToRun
        iterateEffects &&
          iterateEffects.forEach((effectFn) => {
            if (effectFn !== activeEffect) {
              effectsToRun.add(effectFn);
            }
          });
      }

      effectsToRun.forEach((effectFn) => {
        // 如果一个副作用函数存在调度器，则调用该调度器，并将副作用函数作为参数传递
        if (effectFn.options.scheduler) {
          // 新增
          effectFn.options.scheduler(effectFn); // 新增
        } else {
          // 否则直接执行副作用函数
          effectFn();
        }
      });
    };

    function reactive(obj) {
      return new Proxy(obj, {
        /**拦截删除属性*/
        deleteProperty(target, key) {
          /** 检查被操作的属性是否是对象自己的属性 */
          const hadkey = Object.prototype.hasOwnProperty.call(target, key);
          // 使用 Reflect.deleteProperty 完成属性的删除
          const res = Reflect.deleteProperty(target, key);
          if (res && hadkey) {
            // 只有当被删除的属性是对象自己的属性并且成功删除时，才触发更新
            trigger(target, key, "DELETE");
          }
          return res;
        },

        /** 拦截 for...in 循环访问对象*/
        ownKeys(target) {
          // 将副作用函数 与 ITERATE_KEY 关联
          // ownKeys 拿不到具体的操作值，所以使用唯一值进行绑定
          track(target, ITERATE_KEY);
          return Reflect.ownKeys(target);
        },

        /** 拦截 对象的 in 访问对象*/
        has(target, key) {
          track(target, key);
          return Reflect.has(target, key);
        },

        /** receiver 为代理对象 */
        get(target, key, newVal, receiver) {
          // 代理对象可以通过 raw 属性访问原始数据
          if (key === "raw") {
            return target;
          }

          track(target, key);
          // Reflect 接收第三个参数 表示对象 get 的时候调用的对象  解决响应式问题
          return Reflect.get(target, key, receiver); // 新增
        },
        set(target, key, newVal, receiver) {
          // 先获取旧值
          const oldVal = target[key]; // 新增

          // 如果属性不存在，则说明是在添加新属性，否则是设置已有属性
          const type = Object.prototype.hasOwnProperty.call(target, key)
            ? "SET"
            : "ADD";
          // 设置属性值
          const res = Reflect.set(target, key, newVal, receiver);

          // 新增  代理对象可以通过 raw 属读取原始数据
          console.log("打印", target, receiver.raw);
          if (target === receiver.raw) {
            // 比较新值与旧值，只要当不全等的时候才触发响应 新增
            if ((oldVal !== newVal) & (!isNaN(oldVal) || !isNaN(newVal))) {
              // 将 type 作为第三个参数传递给 trigger 函数
              // target[key] = newVal;
              trigger(target, key);
            }
          }
          return res;
        },
      });
    }

    // 注册副作用函数
    effect(() => {
      console.log("执行副作用函数", child.bar);
    });
    setTimeout(() => {
      child.bar = 2;
    }, 1000);
  </script>
</html>
